package kvsql_table

import (
	"container/list"
	"go-kvsql/data_cache"
	"go-kvsql/db/mysqldb"
	"go-kvsql/full_synchronizer"
	"go-kvsql/kv_table"
	"go-kvsql/sql_table"
	"go-kvsql/sync_log_table"
	"go-kvsql/sync_log_table/sync_log"
	"go-kvsql/sync_log_table/sync_log_array"
	"reflect"
	"sync"
)

type KvSqlTable struct {
	dataCache    *data_cache.DataCache
	kvTable      *kv_table.KvTable
	sqlTable     *sql_table.SqlTable
	syncLogTable *sync_log_table.SyncLogTable
	tableType    reflect.Type
	mutexMap     map[int64]*sync.Mutex
	mutex        sync.Mutex
}

func NewKvSqlTable(dbPath string, tableName string, tableStruct interface{},
	mysqlDb *mysqldb.MysqlDb, taskBufferSize int, isRecoverSyncLog bool) (*KvSqlTable, error) {

	syncLogTable, err := sync_log_table.NewSyncLogTable(dbPath, tableName)
	if err != nil {
		return nil, err
	}
	tableType := computeTableType(tableStruct)
	kvTable, err := kv_table.NewKvTable(dbPath, tableName, tableType)
	if err != nil {
		return nil, err
	}
	sqlTable, err := sql_table.NewSqlTable(tableName, mysqlDb, syncLogTable, taskBufferSize)
	if err != nil {
		return nil, err
	}
	kvsqlTable := KvSqlTable{dataCache: data_cache.NewDataCache(), kvTable: kvTable, sqlTable: sqlTable,
		syncLogTable: syncLogTable, tableType: tableType, mutexMap: make(map[int64]*sync.Mutex)}

	if isRecoverSyncLog && !kvsqlTable.IsConsistent() {
		if err = full_synchronizer.Execute(kvTable, sqlTable, syncLogTable, tableType); err != nil {
			return nil, err
		}
	} else {
		kvsqlTable.syncLogTable.Clear()
	}

	return &kvsqlTable, nil
}

func computeTableType(tableStruct interface{}) reflect.Type {
	tableType := reflect.TypeOf(tableStruct)
	if tableType.Kind() == reflect.Ptr {
		tableType = tableType.Elem()
	}
	return tableType
}

func (this *KvSqlTable) getRowMutex(key int64) (rowMutex *sync.Mutex) {
	this.mutex.Lock()
	defer this.mutex.Unlock()
	rowMutex = this.mutexMap[key]
	if rowMutex == nil {
		rowMutex = &sync.Mutex{}
		this.mutexMap[key] = rowMutex
	}
	return
}

func (this *KvSqlTable) Get(key int64) (interface{}, error) {
	rowMutex := this.getRowMutex(key)
	rowMutex.Lock()
	defer rowMutex.Unlock()

	value, err := this.dataCache.Get(key)
	if err != nil {
		return nil, err
	}
	if value != nil {
		return value, nil
	}
	value, err = this.kvTable.Get(key)
	if err != nil {
		return nil, err
	}
	err = this.dataCache.Set(key, value)
	if err != nil {
		return nil, err
	}
	return value, nil
}

func (this *KvSqlTable) Set(key int64, value interface{}) error {
	rowMutex := this.getRowMutex(key)
	rowMutex.Lock()
	defer rowMutex.Unlock()

	syncLog, err := this.syncLogTable.Insert(sync_log.Set, key, value)
	if err != nil {
		return err
	}
	err = this.kvTable.Set(key, syncLog.ValueBytes)
	if err != nil {
		return err
	}
	err = this.dataCache.Set(key, value)
	if err != nil {
		return err
	}
	this.sqlTable.AsyncOperate(*syncLog)
	return nil
}

func (this *KvSqlTable) Del(key int64) error {
	rowMutex := this.getRowMutex(key)
	rowMutex.Lock()
	defer rowMutex.Unlock()

	syncLog, err := this.syncLogTable.Insert(sync_log.Del, key, "")
	if err != nil {
		return err
	}
	err = this.kvTable.Del(key)
	if err != nil {
		this.syncLogTable.Delete(syncLog.LogKeyBytes)
		return err
	}
	this.dataCache.Delete(key)
	this.sqlTable.AsyncOperate(*syncLog)
	return nil
}

func (this *KvSqlTable) IsConsistent() bool {
	return !this.syncLogTable.ContainsData()
}

func (this *KvSqlTable) SyncLogArray() (sync_log_array.SyncLogArray, error) {
	return this.syncLogTable.SortArray(this.tableType)
}

func (this *KvSqlTable) List() *list.List {
	return this.kvTable.List()
}

func (this *KvSqlTable) Count() int {
	return this.kvTable.Count()
}

func (this *KvSqlTable) SyncLogCount() int {
	return this.syncLogTable.Count()
}
